package com.avengers.core.kit;


import com.avengers.core.exception.DurationMemoryInvalidException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.math.BigDecimal;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Function;
import java.util.function.Supplier;

import static com.avengers.core.bean.Numbers.INT_0;

/**
 * @author yu.wang
 * @since 2025/11/13 16:00
 **/
public class DurationMemory<Data> {
    private final Lock initLock;
    private final Duration duration;
    private final Supplier<Data> supplier;
    private final Function<String, Data> function;
    private final Logger logger;

    private Data data;
    private Long initNanoTime;

    private Map<String, Data> dataMap;
    private Map<String, Long> initNanoTimeMap;
    private Map<String, Lock> initLockMap;

    private static final Logger LOGGER_DEFAULT = LoggerFactory.getLogger(DurationMemory.class);

    private DurationMemory(Duration duration, Supplier<Data> supplier, Logger logger) {
        this.duration = duration;
        this.initLock = new ReentrantLock();
        this.logger = logger;
        this.initNanoTime = null;
        this.supplier = supplier;
        this.function = null;
    }

    private DurationMemory(Duration duration, Function<String, Data> function, Logger logger) {
        this.duration = duration;
        this.initLock = new ReentrantLock();
        this.logger = logger;
        this.initNanoTime = null;
        this.supplier = null;
        this.function = function;
    }

    public static <T> DurationMemory<T> of(Duration duration, Supplier<T> supplier) {
        return new DurationMemory<>(duration, supplier, LOGGER_DEFAULT);
    }

    public static <T> DurationMemory<T> of(Duration duration, Supplier<T> supplier, Logger logger) {
        return new DurationMemory<>(duration, supplier, logger);
    }

    public static <T> DurationMemory<T> of(Duration duration, Function<String, T> function) {
        return new DurationMemory<>(duration, function, LOGGER_DEFAULT);
    }

    public static <T> DurationMemory<T> of(Duration duration, Function<String, T> function, Logger logger) {
        return new DurationMemory<>(duration, function, logger);
    }

    private boolean isValid(String key) {
        if (null == this.initNanoTimeMap
                || !this.initNanoTimeMap.containsKey(key)
                || INT_0.longValue() == this.initNanoTimeMap.get(key)
                || null == this.dataMap
                || !this.dataMap.containsKey(key)) {
            return false;
        }
        return System.nanoTime() - this.initNanoTimeMap.get(key) < this.duration.toNanos();
    }

    private Lock getLockFromMap(String key) {
        if (null == this.initLockMap || !this.initLockMap.containsKey(key)) {
            this.initLock.lock();
            try {
                if (null == this.initLockMap || !this.initLockMap.containsKey(key)) {
                    if (null == this.initLockMap) {
                        this.initLockMap = new ConcurrentHashMap<>();
                    }
                    this.initLockMap.put(key, new ReentrantLock());
                }
            } finally {
                this.initLock.unlock();
            }
        }
        return this.initLockMap.get(key);
    }

    public Data get(String key) {
        if (null == this.function) {
            throw new DurationMemoryInvalidException();
        }
        if (null == this.duration) {
            return this.function.apply(key);
        }
        if (isValid(key)) {
            return this.dataMap.get(key);
        }
        Lock lock = this.getLockFromMap(key);
        try {
            lock.lock();
            if (!this.isValid(key)) {
                if (null == this.initNanoTimeMap) {
                    this.initNanoTimeMap = new HashMap<>();
                }
                Long initNanoTime = this.initNanoTimeMap.get(key);
                if (null == initNanoTime) {
                    this.logger.info("init load Map[{}] data begin....", key);
                } else {
                    BigDecimal useTimeMillisecond = TimeKit.useTimeMillisecond(initNanoTime);
                    this.logger.info("reload Map[{}] data after {}ms({}) begin....", key, useTimeMillisecond.longValue(), TimeKit.useTimeStr(useTimeMillisecond.longValue()));
                }
                if (null == this.dataMap) {
                    this.dataMap = new HashMap<>();
                }
                Data tmpData = this.function.apply(key);
                if (null != tmpData) {
                    this.dataMap.put(key, tmpData);
                    this.initNanoTimeMap.put(key, System.nanoTime());
                }
            }
        } finally {
            lock.unlock();
        }
        return this.dataMap.get(key);
    }

    private boolean isValid() {
        return null != this.data && System.nanoTime() - this.initNanoTime < this.duration.toNanos();
    }

    public Data get() {
        if (null == this.supplier) {
            throw new DurationMemoryInvalidException();
        }
        if (null == this.duration) {
            return this.supplier.get();
        }
        if (isValid()) {
            return this.data;
        }
        this.initLock.lock();
        try {
            if (!this.isValid()) {
                if (null == this.initNanoTime) {
                    this.logger.info("init load data begin....");
                } else {
                    BigDecimal useTimeMillisecond = TimeKit.useTimeMillisecond(this.initNanoTime);
                    this.logger.info("reload data after {}ms({}) begin....", useTimeMillisecond.longValue(), TimeKit.useTimeStr(useTimeMillisecond.longValue()));
                }
                this.data = this.supplier.get();
                this.initNanoTime = System.nanoTime();
            }
        } finally {
            this.initLock.unlock();
        }
        return this.data;
    }
}
